package zdream.control.world;

import java.util.*;
import java.util.function.Supplier;

import static zdream.control.world.MHookType.*;

public class MHooks {
	private static final Map<MHookType, Supplier<MHookContext>> FACTORIES = new HashMap<>();

	static {
		FACTORIES.put(TICK, () -> new FoeHookContext(TICK));
		FACTORIES.put(TICK_WHEN_FOE_INACTIVE, () -> new FoeHookContext(TICK_WHEN_FOE_INACTIVE));
		FACTORIES.put(RECEIVE_DAMAGE, DamageContext::new);
		FACTORIES.put(RECOVER, RecoverContext::new);
		FACTORIES.put(UPDATE_PATTERN, () -> new FoeStageUpdateHookContext(UPDATE_PATTERN));
		FACTORIES.put(UPDATE_STAGE, () -> new FoeStageUpdateHookContext(UPDATE_STAGE));
		FACTORIES.put(ADD_BUFF, () -> new BuffContext(ADD_BUFF));
		FACTORIES.put(REMOVE_BUFF, () -> new BuffContext(REMOVE_BUFF));
		FACTORIES.put(REFRESH_GRAVITY, GravityRefreshContext::new);
		FACTORIES.put(ROOM_UPDATED, () -> new MHookContext(ROOM_UPDATED));
		FACTORIES.put(FOE_ADDED, () -> new FoeHookContext(FOE_ADDED));
	}

	public static MHookContext triggerHook(MHookType hookType, MHookStore store) {
		final MHookContext context = FACTORIES.get(hookType).get();
		if (context == null) {
			throw new NullPointerException("你看看你是不是又忘记在 HookType 下创建 FACTORIES 了? hookType = " + hookType);
		}
		store.fill(context);
		return context;
	}

	public static class MHookContext {
		public final MHookType hookType;
		public final List<MHookStore.HookHandler> handlers = new ArrayList<>();
		public boolean aborted = false;
		public MHookStore.HookHandler abortBy;

		public MHookContext(MHookType hookType) {
			this.hookType = Objects.requireNonNull(hookType);
		}

		@SuppressWarnings("unchecked")
		public <C extends MHookContext> C as() {
			return (C) this;
		}

		public void run() {
			if (handlers.isEmpty()) {
				return;
			}

			handlers.sort(null);
			for (MHookStore.HookHandler handler : handlers) {
				handler.handler.accept(this);
				if (this.aborted) {
					abortBy = handler;
					break;
				}
			}
		}

		public void abort() {
			this.aborted = true;
		}

		public <T extends IHookStorage> MHookContext appendAll(Collection<T> collection) {
			for (IHookStorage storage : collection) {
				storage.getHookStore().fill(this);
			}
			return this;
		}

		public MHookContext append(IHookStorage storage) {
			storage.getHookStore().fill(this);
			return this;
		}

		public MHookContext append(IHookStorage storage, MHookType type) {
			storage.getHookStore().fill(this, type);
			return this;
		}
	}

	public static class FoeHookContext extends MHookContext {
		public MFoe foe;
		public FoeHookContext(MHookType hookType) {
			super(hookType);
		}
		public FoeHookContext withFoe(MFoe foe) {
			this.foe = foe;
			return this;
		}

		public void run() {
			if (handlers.isEmpty()) {
				return;
			}

			handlers.sort(null);
			for (MHookStore.HookHandler handler : handlers) {
				handler.handler.accept(this);
				if (this.aborted || foe.isDestroyed()) {
					abortBy = handler;
					break;
				}
			}
		}
	}

	public static class BuffContext extends MHookContext {
		public MFoe foe;
		public MFoeBuff buff;
		public BuffContext(MHookType hookType) {
			super(hookType);
		}
		public BuffContext withFoe(MFoe foe) {
			this.foe = foe;
			return this;
		}
		public BuffContext withBuff(MFoeBuff buff) {
			this.buff = buff;
			return this;
		}
	}

	public static class FoeStageUpdateHookContext extends MHookContext {
		public MFoe foe;
		public String lastPattern, currentPattern;
		public FoeStageUpdateHookContext(MHookType type) {
			super(type);
		}
		public FoeStageUpdateHookContext withFoe(MFoe foe) {
			this.foe = foe;
			return this;
		}
		public FoeStageUpdateHookContext withChanged(String from, String to) {
			this.lastPattern = from;
			this.currentPattern = to;
			return this;
		}
	}

	/**
	 * 还是一样, priority=0 的时候执行扣血, 想要之前执行的 priority < 0, 之后执行的 priority > 0
	 *
	 * <li>接受且掉血了: accepted = true
	 * <li>接受但吸收了 (有可能防御方在无敌时间): absorbed = true
	 * <li>弹飞: accepted = true
	 * <li>忽略: 其它 (可直接 aborted)
	 * </li>
	 *
	 * 如果是 blocked 表示被弹飞, 如果不 blocked 也不 accepted 表示忽略.
	 */
	public static class DamageContext extends MHookContext {
		public MFoe attacker, defender;
		public int damage, level, hpFrom, hpTo;
		public boolean blocked, accepted, absorbed;
		public boolean targetDie;
		public boolean executeWhenFoeIsInactive;

		public DamageContext() {
			super(RECEIVE_DAMAGE);
		}
		public DamageContext withAttacker(MFoe attacker) {
			this.attacker = attacker;
			return this;
		}
		public DamageContext withDefender(MFoe defender) {
			this.defender = defender;
			return this;
		}
		public DamageContext withDamage(int damage, int level) {
			this.damage = damage;
			this.level = level;
			return this;
		}
		public DamageContext withHpFrom(int hpFrom) {
			this.hpFrom = hpFrom;
			return this;
		}
		public DamageContext withHpTo(int hpTo) {
			this.hpTo = hpTo;
			return this;
		}
		public DamageContext executeInactive() {
			this.executeWhenFoeIsInactive = true;
			return this;
		}

		public void block(MFoeBuff by) {
			this.abort();
			this.blocked = true;
		}
		public void absorb() {
			this.absorbed = true;
		}
		public void accept() {
			this.accepted = true;
		}
		public void markTargetDie() {
			this.targetDie = true;
		}
	}

	/**
	 * 还是一样, priority=0 的时候执行扣血, 想要之前执行的 priority < 0, 之后执行的 priority > 0
	 */
	public static class RecoverContext extends MHookContext {
		public MFoe foe, causeBy;
		public int value, hpFrom, hpTo;
		public boolean executeWhenFoeIsInactive;

		public RecoverContext() {
			super(RECOVER);
		}
		public RecoverContext withFoe(MFoe foe) {
			this.foe = foe;
			return this;
		}
		public RecoverContext withCauseBy(MFoe causeBy) {
			this.causeBy = causeBy;
			return this;
		}
		public RecoverContext withValue(int value) {
			this.value = value;
			return this;
		}
		public RecoverContext withHpFrom(int hpFrom) {
			this.hpFrom = hpFrom;
			return this;
		}
		public RecoverContext withHpTo(int hpTo) {
			this.hpTo = hpTo;
			return this;
		}
		public RecoverContext executeInactive() {
			this.executeWhenFoeIsInactive = true;
			return this;
		}
	}

	public static class GravityRefreshContext extends MHookContext {
		public MFoe foe;
		/**
		 * @see MFoe#gravityScale
		 */
		public float scale;
		/**
		 * @see MFoe#gravityDown
		 */
		public boolean down;

		public GravityRefreshContext() {
			super(REFRESH_GRAVITY);
		}
		public GravityRefreshContext withFoe(MFoe foe) {
			this.foe = foe;
			return this;
		}
		public GravityRefreshContext withGravityScale(float scale, boolean down) {
			this.scale = scale;
			this.down = down;
			return this;
		}
	}
}
